#!/usr/bin/env jruby

require 'test/unit'
require 'pathname'
dir = File.dirname(__FILE__)
require File.join(dir, '..', '..', 'lib', 'auxil', 'storagesize')

########################################

module Constants4StorageSizeTests
  PREFIXES = 'kmgtpezy'.split('')
  POWERS = (1..PREFIXES.size).to_a
  INDICES = 0...PREFIXES.size
  MULTIPLIERS = [1024, 1000]
  
  INTEGER, FLOAT, STRING, TIME = 12345, 123.45, 'foo', Time.now
  
  S0 =   StorageSize.new(  0)
  S2 =   StorageSize.new(  2)
  S3 =   StorageSize.new(  3)
  S5 =   StorageSize.new(  5)
  S123 = StorageSize.new(123)
  
  DELTA = 1e-6
end

########################################

class TestStorageSizeMultiplier < Test::Unit::TestCase
  include Constants4StorageSizeTests
  
  def test_should_accept_integer
    assert_nothing_thrown { StorageSize.multiplier = INTEGER }
  end
  
  def test_should_reject_noninteger
    assert_raise(TypeError, 'f') { StorageSize.multiplier = FLOAT }
    assert_raise(TypeError, 's') { StorageSize.multiplier = STRING }
    assert_raise(TypeError, 't') { StorageSize.multiplier = TIME }
  end
  
  def test_should_check_value
    assert_raise(RangeError, '-5') { StorageSize.multiplier = -5 }
    assert_raise(RangeError,  '0') { StorageSize.multiplier = 0 }
    assert_nothing_thrown('5') { StorageSize.multiplier = 5 }
  end
  
  def teardown
    assert StorageSize.multiplier > 0, 'invariant'
  end
end

########################################

class TestStorageSizeNew < Test::Unit::TestCase
  include Constants4StorageSizeTests
  
  def setup
    @save_mult = StorageSize.multiplier
  end
  
  def test_should_accept_integer
    assert_nothing_thrown { StorageSize.new(INTEGER) }
  end
  
  def test_should_reject_noninteger
    assert_raise(TypeError, 'f') { StorageSize.new(FLOAT) }
    assert_raise(TypeError, 's') { StorageSize.new(STRING) }
    assert_raise(TypeError, 't') { StorageSize.new(TIME) }
  end
  
  def test_should_create_correct_size
    numbers = [0, 1, 99999999999999999999999999999999999999999999]
    numbers.each do |i|
      assert_equal i, StorageSize.new(i).to_i, "#{i}"
    end
  end
  
  def teardown
    assert_equal @save_mult, StorageSize.multiplier, 'invariant'
  end
end

########################################

class TestStorageSizeFromX < Test::Unit::TestCase
  include Constants4StorageSizeTests
  
  METHODS = PREFIXES.map { |pre| ('from_' + pre).to_sym }
  
  def test_should_accept_numeric
    METHODS.each do |meth|
      assert_nothing_thrown(
        "#{meth} i") { StorageSize.send(meth, INTEGER) }
      assert_nothing_thrown(
        "#{meth} f") { StorageSize.send(meth, FLOAT) }
    end
  end
  
  def test_should_reject_nonnumeric
    METHODS.each do |meth|
      assert_raise(TypeError, 
        "#{meth} s") { StorageSize.send(meth, STRING) }
      assert_raise(TypeError, 
        "#{meth} t") { StorageSize.send(meth, TIME) }
    end
  end
  
  def test_should_check_value
    METHODS.each do |meth|
      assert_raise(RangeError, 
        "#{meth} -5") { StorageSize.send(meth, -5) }
      assert_nothing_thrown(
        "#{meth} 0") { StorageSize.send(meth, 0) }
      assert_nothing_thrown(
        "#{meth} 5") { StorageSize.send(meth, 5) }
    end
  end
  
  def test_should_create_correct_size
    INDICES.each do |i|
      mult = MULTIPLIERS[rand(2)]
      StorageSize.multiplier = mult
      meth = METHODS[i]
      x = [0, 1, 2, 4, 7][rand(5)]  # whatever
      expected = x * mult**POWERS[i]
      ss = StorageSize.send(meth, x)
      assert_equal expected, ss.to_i, "#{meth} #{mult} #{x}"
    end
  end
  
  def teardown
    assert StorageSize.multiplier > 0, 'invariant'
  end
end

########################################

class TestStorageSizeBracket < Test::Unit::TestCase
  include Constants4StorageSizeTests
  
  PATH1 = __FILE__
  SIZE1 = FileTest.size(PATH1)
  PATH2 = $LOADED_FEATURES.find {|fea| fea =~ /storagesize/ }
  SIZE2 = FileTest.size(PATH2)
  
  def test_should_accept_string
    assert_nothing_thrown('PATH1') { StorageSize[PATH1] }
    assert_nothing_thrown('empty') { StorageSize[''] }
  end
  
  # 2/3/08: The commented-out empty path tests fail since
  # switching from JRuby 1.0 to JRuby 1.0.3. Hmmm.
  def test_should_accept_filelike
    assert_nothing_thrown('fs p') { StorageSize[File.stat(PATH1)] }
    #assert_nothing_thrown('fs e') { StorageSize[File.stat('')] }
    assert_nothing_thrown('pn p') { StorageSize[Pathname.new(PATH1)] }
    #assert_nothing_thrown('pn e') { StorageSize[Pathname.new('')] }
  end
  
  def test_should_reject_bad_type
    assert_raise(TypeError, 'i') { StorageSize[INTEGER] }
    assert_raise(TypeError, 'f') { StorageSize[FLOAT] }
    assert_raise(TypeError, 't') { StorageSize[TIME] }
    assert_raise(TypeError, 'file') { StorageSize[File.new(PATH1)] }
  end
  
  def test_should_create_correct_size
    assert_equal SIZE1, StorageSize[PATH1].to_i, 'PATH1'
    assert_equal SIZE2, StorageSize[PATH2].to_i, 'PATH2'
  end
end

########################################

class TestStorageSizeToS < Test::Unit::TestCase
  def test_should_pluralize_correctly
    regex = /s$/i
    assert_match regex, StorageSize.new(-2).to_s
    assert_match regex, StorageSize.new(0).to_s
    assert_match regex, StorageSize.new(2).to_s
    assert_no_match regex, StorageSize.new(-1).to_s
    assert_no_match regex, StorageSize.new(1).to_s
  end
end

########################################

class TestStorageSizeToBest < Test::Unit::TestCase
  include Constants4StorageSizeTests
  
  def test_should_be_correct_for_small_values
    MULTIPLIERS.each do |mult|
      StorageSize.multiplier = mult
      [-(mult - 1), 0, (mult - 1)].each do |i|
        ss = StorageSize.new(i)
        assert_equal ss.to_s, ss.to_best, "#{ss}"
      end
    end
  end
  
  def test_should_be_correct_for_kilo
    MULTIPLIERS.each do |mult|
      StorageSize.multiplier = mult
      regex = /^-?1\.0 *k *b/i
      [-(mult + 1), -mult, mult, (mult + 1)].each do |i|
        ss = StorageSize.new(i)
        assert_match regex, ss.to_best, "#{ss}"
      end
    end
  end
  
  def test_should_be_correct_for_powers
    INDICES.each do |i|
      mult = MULTIPLIERS[rand(2)]
      StorageSize.multiplier = mult
      regex = /^1\.0 *#{PREFIXES[i]} *b/i
      ss = StorageSize.new(mult**POWERS[i])
      assert_match regex, ss.to_best, "#{ss}"
    end
  end
  
  def teardown
    assert StorageSize.multiplier > 0, 'invariant'
  end
end

########################################

class TestStorageSizeCompare < Test::Unit::TestCase
  include Constants4StorageSizeTests
  
  def test_should_accept_storage_size
    assert_nothing_thrown { S0 <=> S0 }
  end
  
  def test_should_accept_numeric
    assert_nothing_thrown('i') { S0 <=> INTEGER }
    assert_nothing_thrown('f') { S0 <=> FLOAT }
  end
  
  def test_should_reject_others
    assert_raise(TypeError, 's') { S0 <=> STRING }
    assert_raise(TypeError, 't') { S0 <=> TIME }
  end
  
  def test_should_compare_storage_size
    assert S0 < S123, '0 < 123'
    assert S0 == S0, '0 = 0'
    assert S123 > S0, '123 > 0'
  end
  
  def test_should_compare_numeric
    assert S123 < 124, '124'
    assert S123 == 123, '123'
    assert S123 == 123.0, '123.0'
    assert S123 > 122.9, '122.9'
  end
  
  def test_should_compare_numeric_coercion
    assert 122 < S123, '122'
    assert 122.0 < S123, '122.0'
  end
end

########################################

class TestStorageSizeAddSubtract < Test::Unit::TestCase
  include Constants4StorageSizeTests
  
  OPERATORS = [:+, :-]
  
  def test_should_accept_storage_size
    OPERATORS.each do |op|
      assert_nothing_thrown("#{op}") { S123.send(op, S5) }
    end
  end
  
  def test_should_reject_others
    OPERATORS.each do |op|
      assert_raise(TypeError, "#{op} i") { S123.send(op, INTEGER) }
      assert_raise(TypeError, "#{op} f") { S123.send(op, FLOAT) }
      assert_raise(TypeError, "#{op} s") { S123.send(op, STRING) }
      assert_raise(TypeError, "#{op} t") { S123.send(op, TIME) }
    end
  end
  
  def test_should_reject_numeric_coercion
    OPERATORS.each do |op|
      assert_raise(TypeError, "#{op} i") { INTEGER.send(op, S123) }
      assert_raise(TypeError, "#{op} f") { FLOAT.send(op, S123) }
    end
  end
  
  def test_should_return_storage_size
    OPERATORS.each do |op|
      assert_kind_of(StorageSize, S123.send(op, S5), "#{op}")
    end
  end
  
  def test_should_add_values
    assert_equal 128, (S5 + S123).to_i
  end
  
  def test_should_subtract_values
    assert_equal -118, (S5 - S123).to_i
  end
end

########################################

class TestStorageSizeMultiply < Test::Unit::TestCase
  include Constants4StorageSizeTests
  
  def test_should_accept_numeric
    assert_nothing_thrown('i') { S0 * INTEGER }
    assert_nothing_thrown('f') { S0 * FLOAT }
  end
  
  def test_should_reject_storage_size
    assert_raise(TypeError) { S123 * S5 }
  end
  
  def test_should_reject_others
    assert_raise(TypeError, 's') { S123 * STRING }
    assert_raise(TypeError, 't') { S123 * TIME }
  end
  
  def test_should_accept_numeric_coercion
    assert_nothing_thrown('i') { INTEGER * S123 }
    assert_nothing_thrown('f') { FLOAT * S123 }
  end
  
  def test_should_return_storage_size
    assert_kind_of StorageSize, S123 * INTEGER, 'i normal'
    assert_kind_of StorageSize, INTEGER * S123, 'i coerce'
    assert_kind_of StorageSize, S123 * FLOAT, 'f normal'
    assert_kind_of StorageSize, FLOAT * S123, 'f coerce'
  end
  
  def test_should_multiply_values
    assert_equal 615, (S123 * 5).to_i, 'normal'
    assert_equal 615, (123 * S5).to_i, 'coerce'
    assert_equal 12, (S5 * 2.4).to_i, 'normal'
    assert_equal 12, (2.4 * S5).to_i, 'coerce'
  end
end

########################################

class TestStorageSizeDivide < Test::Unit::TestCase
  include Constants4StorageSizeTests
  
  def test_should_accept_numeric
    assert_nothing_thrown('i') { S5 / INTEGER }
    assert_nothing_thrown('f') { S5 / FLOAT }
  end
  
  def test_should_accept_storage_size
    assert_nothing_thrown { S5 / S3 }
  end
  
  def test_should_reject_others
    assert_raise(TypeError, 's') { S5 / STRING }
    assert_raise(TypeError, 't') { S5 / TIME }
  end
  
  def test_should_reject_numeric_coercion
    assert_raise(TypeError, 'i') { INTEGER / S5 }
    assert_raise(TypeError, 'f') { FLOAT / S5 }
  end
  
  def test_should_return_storage_size
    assert_kind_of StorageSize, S5 / INTEGER, 'i'
    assert_kind_of StorageSize, S5 / FLOAT, 'f'
  end
  
  def test_should_return_integer
    assert_kind_of Integer, S5 / S3
  end
  
  def test_should_divide_values
    assert_equal 2, S5 / S2, 'S2'
    assert_equal 2, S5 / 2, '2'
    assert_equal 2, S5 / 2.0, '2.0'
  end
end
